#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsServer.h>
#include <ArduinoJson.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <SPIFFS.h>
#include <DNSServer.h>
#include <Preferences.h>
#include <EEPROM.h>
#define EEPROM_SIZE 8
// WiFi配置
struct Boot_Times {
  int Counter = 0;
  bool fail = false;
};
const char* AP_SSID = "System_AP_Config";
const char* CONFIG_FILE = "/WIFI_config.txt";
bool isAPMode = false;
WebServer server(80);
DNSServer dnsServer;
Preferences preferences;

IPAddress apIP(192, 168, 4, 1);       // AP模式IP地址
IPAddress subnet(255, 255, 255, 0);   // 子网掩码

// LED配置
const int ledRed = 36;  
const int ledYellow = 37;
bool lock = false;
// 时间服务
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org");

// Web服务器
WebSocketsServer webSocket = WebSocketsServer(81);

// 任务结构体
struct Task {
    String name;
    time_t start;
    time_t end;
    bool triggeredStart;
    bool triggeredEnd;
};

const int MAX_TASKS = 20;
Task tasks[MAX_TASKS];
int taskCount = 0;
String device_Name = "教室管理系统";
bool connectToWiFi() {
  if (!SPIFFS.exists(CONFIG_FILE)) return false;

  File file = SPIFFS.open(CONFIG_FILE, "r");
  if (!file) return false;

  String ssid = file.readStringUntil('\n');
  String password = file.readStringUntil('\n');
  device_Name = file.readStringUntil('\n');
  file.close();

  ssid.trim();
  password.trim();

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid.c_str(), password.c_str());
  Serial.printf("\nConnecting to %s...", ssid.c_str());
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);
    Serial.print(".");
    attempts++;
  }

  if (WiFi.status() != WL_CONNECTED) {
    return false;
  }
  return true;
}
String ConfigPage() {
  String page = R"(
  <!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>WiFi 配置</title>
  <style>
    :root {
      --primary-color: #3498db;
      --secondary-color: #2980b9;
      --success-color: #2ecc71;
      --error-color: #e74c3c;
      --background-color: #f0f0f0;
      --container-background: #fff;
      --text-color: #333;
    }
    body {
      font-family: Arial, sans-serif;
      background: var(--background-color);
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      margin: 0;
      padding: 20px;
      height: 100vh;
      text-align: center;
    }
    .container {
      background: var(--container-background);
      padding: 20px;
      border-radius: 10px;
      box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
      width: 90%;
      max-width: 400px;
      text-align: center;
    }
    h2 {
      font-size: 24px;
      color: var(--text-color);
      margin-bottom: 20px;
    }
    button {
      padding: 12px 24px;
      background: var(--primary-color);
      color: white;
      border: none;
      border-radius: 6px;
      cursor: pointer;
      font-size: 16px;
      transition: all 0.3s;
    }
    button:hover {
      background: var(--secondary-color);
      transform: translateY(-1px);
      box-shadow: 0 5px 15px rgba(52, 152, 219, 0.3);
    }
    #scanBtn {
      background: var(--primary-color);
    }
    #networks {
      margin: 20px 0;
      text-align: left;
      border: 1px solid #ddd;
      border-radius: 6px;
      padding: 10px;
      max-height: 200px;
      overflow-y: auto;
    }
    .network-item {
      padding: 10px;
      border-bottom: 1px solid #ddd;
      cursor: pointer;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .network-item:last-child {
      border-bottom: none;
    }
    .network-item:hover {
      background-color: #f9f9f9;
    }
    .signal {
      color: #666;
    }
    input {
      padding: 10px;
      margin: 5px 0;
      width: 80%;
      max-width: 300px;
      border: 1px solid #ddd;
      border-radius: 6px;
      font-size: 16px;
      text-align: center;
    }
    form {
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    form button {
      margin-top: 10px;
      background: var(--success-color);
    }
    form button:hover {
      background: #70d29c;
    }
    .show-password {
      display: flex;
      align-items: center;
      margin: 10px 0;
    }
    .show-password input[type="checkbox"] {
      width: auto;
      margin-right: 8px;
    }
  </style>
</head>
<body>
  <div class="container">
    <h2>WiFi 配置</h2>
    <button id="scanBtn" onclick="scanNetworks() ">扫描WiFi网络</button>
    <p id="status"></p>
    <div id="networks"></div>
    <form action="/config" method="post">
      <input type="text" name="ssid" placeholder="WiFi名称" required>
      <input type="password" name="password" id="password" placeholder="WiFi密码" required>
      <input type="text" name="name" placeholder="教室名称" required>
      <div class="show-password">
        <input type="checkbox" id="showPassword" onclick="togglePassword() ">
        <label for="showPassword">显示密码</label>
      </div>
      <button type="submit">保存并连接</button>
    </form>
  </div>
<script>
  function scanNetworks() {
    document.getElementById('scanBtn').disabled = true;
    document.getElementById('status').innerHTML = '扫描中...';
    fetch('/scan')
      .then(response => response.json())
      .then(data => {
        showNetworks(data);
        document.getElementById('scanBtn').disabled = false;
        document.getElementById('status').innerHTML = '';
      })
      .catch(error => {
        console.error(error);
        document.getElementById('status').innerHTML = '扫描失败';
        document.getElementById('scanBtn').disabled = false;
      });
  }
  function showNetworks(networks) {
    const list = document.getElementById('networks');
    list.innerHTML = networks.map(network => `
      <div class="network-item" onclick="selectNetwork('${network.ssid}') ">
        ${network.ssid}
        <span class="signal">
          ${getSignalBars(network.rssi)} 
          (${network.encryption})
        </span>
      </div>
    `).join('');
  }
  function getSignalBars(rssi) {
    const bars = '▮▮▮▮';
    const quality = Math.min(Math.max(2 * (rssi + 100), 0), 100);
    return bars.slice(0, Math.floor(quality / 25));
  }
  function selectNetwork(ssid) {
    document.getElementsByName('ssid')[0].value = ssid;
    document.getElementById('password').focus();
  }
  function togglePassword() {
    const passwordInput = document.getElementById('password');
    const showPassword = document.getElementById('showPassword');
    if (showPassword.checked) {
      passwordInput.type = 'text';
    } else {
      passwordInput.type = 'password';
    }
  }
</script>
</body>
</html>
  )";
  return page;
}

void startAPMode() {
  preferences.begin("wifi-config", false);
  WiFi.mode(WIFI_AP_STA);  // 同时启用AP和STA模式
  WiFi.softAPConfig(apIP, apIP, subnet);
  WiFi.softAP(AP_SSID);
  Serial.printf("\nAP Mode: %s\n", AP_SSID);
  Serial.print("IP address: ");
  Serial.println(WiFi.softAPIP());
  dnsServer.start(53, "*", apIP);
  server.on("/scan", HTTP_GET, []() {
    handleWiFiScan();
  });
  server.on("/", HTTP_GET, []() {
    server.send(200, "text/html", ConfigPage());
  });

  server.on("/config", HTTP_POST, []() {
    String ssid = server.arg("ssid");
    String password = server.arg("password");
    device_Name = server.arg("name");
    if (ssid.length() == 0) {
      server.send(400, "text/plain", "SSID cannot be empty");
      return;
    }
    WiFi.begin(ssid, password);
    Serial.printf("\nConnecting to %s...", ssid.c_str());
    int attempts = 0;
    while (WiFi.status() != WL_CONNECTED && attempts < 20) {
      delay(500);
      Serial.print(".");
      attempts++;
    }
    if (WiFi.status() != WL_CONNECTED) {
      SPIFFS.remove(CONFIG_FILE);
      server.send(200, "text/html", 
        "<html><head><meta charset=\"UTF-8\"><title>设备重启中</title><style>body{font-family:Arial,sans-serif;background-color:#f4f4f4;display:flex;justify-content:center;align-items:center;height:100vh;margin:0}"
        ".container{background-color:#fff;padding:20px;border-radius:8px;box-shadow:0 0 10px rgba(0,0,0,0.1);text-align:center}h3{font-size:20px;color:#333;margin:0}p{font-size:14px;color:#777;margin-top:10px}</style></head>"
        "<body><div class=\"container\"><h3>连接失败,请重新配置</h3>"
        "<p>请转至WiFi配置页面</p></div></body></html>");
    } else {
      File file = SPIFFS.open(CONFIG_FILE, "w");
      if (!file) {
        server.send(500, "text/plain", "Failed to save config");
        delay(6000);
        ESP.restart();
        return;
      }
      file.println(ssid);
      file.println(password);
      file.println(device_Name);
      file.close();
      server.send(200, "text/html", 
        "<html><head><meta charset=\"UTF-8\"><title>设备重启中</title><style>body{font-family:Arial,sans-serif;background-color:#f4f4f4;display:flex;justify-content:center;align-items:center;height:100vh;margin:0}"
        ".container{background-color:#fff;padding:20px;border-radius:8px;box-shadow:0 0 10px rgba(0,0,0,0.1);text-align:center}h3{font-size:20px;color:#333;margin:0}p{font-size:14px;color:#777;margin-top:10px}</style></head>"
        "<body><div class=\"container\"><h3>配置已保存,请记录IP地址:"+WiFi.localIP().toString()+"</h3>"
        "<p>请稍候片刻,设备将在重启后应用新配置</p></div></body></html>");
      delay(6000);
      ESP.restart();
    }
  });
  // 处理Captive Portal重定向
  server.onNotFound([]() {
    server.sendHeader("Location", "http://" + apIP.toString(), true);
    server.send(302, "text/plain", "");
  });
  server.begin();
}

// WiFi扫描处理函数
void handleWiFiScan() {
  DynamicJsonDocument doc(1024);
  JsonArray networks = doc.to<JsonArray>();

  // 执行WiFi扫描
  int n = WiFi.scanNetworks();
  Serial.printf("Found %d networks\n", n);

  for (int i = 0; i < n; ++i) {
    JsonObject network = networks.createNestedObject();
    network["ssid"] = WiFi.SSID(i);
    network["rssi"] = WiFi.RSSI(i);
    network["encryption"] = getEncryptionType(WiFi.encryptionType(i));
  }

  String response;
  serializeJson(doc, response);
  server.send(200, "application/json", response);
}

// 加密类型转换
String getEncryptionType(uint8_t type) {
  switch(type) {
    case 7: return "Open";
    case 5:  return "WEP";
    case 2: return "WPA";
    case 4: return "WPA2";
    case 8: return "Auto";
    default:            return "Unknown";
  }
}


bool initFS() {
  if (!SPIFFS.begin()) {
    Serial.println("Formatting filesystem...");
    SPIFFS.format();
    return SPIFFS.begin();
  }
  return true;
}

void Light_Control(void *parameter) {
  while (true) {
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }  
}

void setup() {
  pinMode(ledRed,OUTPUT);
  pinMode(ledYellow,OUTPUT);
  EEPROM.begin(EEPROM_SIZE);
  Boot_Times boot_times;
  EEPROM.get(0, boot_times);
  if (boot_times.fail == false) {
    boot_times.Counter = 0;
  }
  boot_times.Counter++;
  boot_times.fail = true;
  EEPROM.put(0, boot_times);
  EEPROM.commit();
  Serial.begin(115200);
  if (!initFS()) {
    Serial.println("Failed to initialize filesystem");
    while (1)
      ;
  }

  // 连接WiFi
  if (boot_times.Counter <= 3 && connectToWiFi()) {
    //startWebServer();
    Serial.println("\nSystem Ready!");
    Serial.print("Web Interface: http://");
    Serial.println(WiFi.localIP());
    // 连接成功，执行主程序
    Serial.println("Connected to WiFi!");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    // 初始化时间客户端
    timeClient.begin();
    timeClient.setTimeOffset(8 * 3600);  // UTC时间 东八区
    // 启动Web服务器
    server.on("/", handleRoot);
    server.begin();
    // 启动WebSocket
    webSocket.begin();
    webSocket.onEvent(webSocketEvent);
    xTaskCreatePinnedToCore(
      Light_Control,
      "Light Controller",
      4096,
      nullptr,
      2,
      nullptr,
      PRO_CPU_NUM);
  } else {
    // 连接失败，进入AP模式
    boot_times.Counter == 0;
    isAPMode = true;
    startAPMode();
  }
  boot_times.fail = false;
  EEPROM.put(0, boot_times);
  EEPROM.commit();
}

void loop() {
  server.handleClient();
  if (WiFi.getMode() == WIFI_AP) {
    dnsServer.processNextRequest();
  }
  if (!isAPMode) {
    webSocket.loop();
    timeClient.update();
    checkTasks();
  }

  vTaskDelay(1000 / portTICK_PERIOD_MS);
}
void handleRoot() {
    String html = R"(
    <html><head><meta charset="UTF-8">
    <title>教室课程管理</title>
    <meta http-equiv="refresh" content="30">
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            background-color: #f0f0f0;
        }
        .container {
            background: #fff;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
            max-width: 600px;
            margin: 0 auto;
            text-align: center;
        }
        .task-list {
            max-height: 400px;
            overflow-y: auto;
            margin-top: 20px;
            padding: 0 20px;
        }
        .task-item {
            margin: 10px 0;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        .active {
            background-color: #e6ffe6; /* Light green */
        }
        .expired {
            background-color: #ffe6e6; /* Light red */
        }
        h1 {
            font-size: 24px;
            color: #333;
        }
        .time {
            font-size: 14px;
            color: #666;
        }
    </style>
    </head>
    <body>
    <div class="container">
        <h1>当前课程 (总计: )";

    html += taskCount;
    html += R"()</h1>
        <div class="time">当前时间: )";

    html += formatTime(timeClient.getEpochTime());

    html += R"(</div>
        <div class="task-list">)";

    if (taskCount == 0) {
        html += R"(<div>没有课程</div>)";
    } else {
        for (int i = 0; i < taskCount; i++) {
            html += R"(<div class="task-item)";
            if (timeClient.getEpochTime() >= tasks[i].end) {
                html += R"( expired)";
            } else if (timeClient.getEpochTime() >= tasks[i].start) {
                html += R"( active)";
            }
            html += R"(">
                <strong>)";
            html += tasks[i].name;
            html += R"(</strong><br>
                开始时间: )";
            html += formatTime(tasks[i].start);
            html += R"(<br>
                结束时间: )";
            html += formatTime(tasks[i].end);
            html += R"(</div>)";
        }
    }

    html += R"(</div>
    </div>
    </body></html>)";

    server.send(200, "text/html", html);
}


void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
    if (type == WStype_TEXT) {
        DynamicJsonDocument doc(512);
        deserializeJson(doc, payload);
        if(doc["command"] == "identify") {
            DynamicJsonDocument res(256);
            res["type"] = "classroom_manager";
            res["name"] = device_Name;
            String output;
            serializeJson(res, output);
            webSocket.sendTXT(num, output);
        } else if(doc["command"] == "add_task") {
          if (taskCount < MAX_TASKS) {
              tasks[taskCount].name = doc["name"].as<String>();
              tasks[taskCount].start = parseTime(doc["start"].as<String>());
              tasks[taskCount].end = parseTime(doc["end"].as<String>());
              tasks[taskCount].triggeredStart = false;
              tasks[taskCount].triggeredEnd = false;
              taskCount++;
          }
        }
    }
}

time_t parseTime(String timeStr) {
    struct tm tm;
    sscanf(timeStr.c_str(), "%d-%d-%dT%d:%d:%d",
           &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
           &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
    tm.tm_year -= 1900;
    tm.tm_mon -= 1;
    return mktime(&tm);
}

void checkTasks() {
    time_t now = timeClient.getEpochTime();
    bool ledState = false;
    bool lock = true;
    for (int i = 0; i < taskCount; i++) {
        if (!tasks[i].triggeredStart && now >= tasks[i].start) {
            tasks[i].triggeredStart = true;
        }
        if (!tasks[i].triggeredEnd && now >= tasks[i].end) {
            tasks[i].triggeredEnd = true;
        }
    }
    if (lock == true) {
      digitalWrite(ledRed,HIGH);
      digitalWrite(ledYellow,LOW);
    } else {
      digitalWrite(ledRed,LOW);
      digitalWrite(ledYellow,HIGH);
    }
}

String formatTime(time_t timestamp) {
    struct tm *tm = localtime(&timestamp);
    char buffer[20];
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm);
    return String(buffer);
}